/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package thrift

import (
	"container/list"
	"strconv"
)

/**
 * Type constants in the Thrift protocol.
 */
type TType byte

const (
	STOP    = 0
	VOID    = 1
	BOOL    = 2
	BYTE    = 3
	I08     = 3
	DOUBLE  = 4
	I16     = 6
	I32     = 8
	I64     = 10
	STRING  = 11
	UTF7    = 11
	STRUCT  = 12
	MAP     = 13
	SET     = 14
	LIST    = 15
	ENUM    = 16
	UTF8    = 16
	UTF16   = 17
	GENERIC = 127
)

func (p TType) String() string {
	switch p {
	case STOP:
		return "STOP"
	case VOID:
		return "VOID"
	case BOOL:
		return "BOOL"
	case BYTE:
		return "BYTE"
	case DOUBLE:
		return "DOUBLE"
	case I16:
		return "I16"
	case I32:
		return "I32"
	case I64:
		return "I64"
	case STRING:
		return "STRING"
	case STRUCT:
		return "STRUCT"
	case MAP:
		return "MAP"
	case SET:
		return "SET"
	case LIST:
		return "LIST"
	case ENUM:
		return "ENUM"
	case UTF16:
		return "UTF16"
	case GENERIC:
		return "GENERIC"
	}
	return "Unknown"
}

func (p TType) IsBaseType() bool {
	switch p {
	case BOOL, BYTE, DOUBLE, I16, I32, I64, STRING, UTF8, UTF16:
		return true
	default:
		return false
	}
	return false
}

func (p TType) IsEmptyType() bool {
	switch p {
	case STOP, VOID:
		return true
	default:
		return false
	}
	return false
}

func (p TType) IsEnum() bool {
	switch p {
	case ENUM:
		return true
	default:
		return false
	}
	return false
}

func (p TType) IsNumericType() bool {
	switch p {
	case ENUM, BOOL, BYTE, DOUBLE, I16, I32, I64:
		return true
	default:
		return false
	}
	return false
}

func (p TType) IsStringType() bool {
	switch p {
	case STRING, UTF8, UTF16:
		return true
	default:
		return false
	}
	return false
}

func (p TType) IsContainer() bool {
	switch p {
	case MAP, SET, LIST:
		return true
	default:
		return false
	}
	return false
}

func (p TType) IsStruct() bool {
	switch p {
	case STRUCT:
		return true
	default:
		return false
	}
	return false
}

func (p TType) IsMap() bool {
	switch p {
	case MAP:
		return true
	default:
		return false
	}
	return false
}

func (p TType) IsList() bool {
	switch p {
	case LIST:
		return true
	default:
		return false
	}
	return false
}

func (p TType) IsSet() bool {
	switch p {
	case SET:
		return true
	default:
		return false
	}
	return false
}

func (p TType) IsInt() bool {
	switch p {
	case BYTE, I16, I32, I64:
		return true
	default:
		return false
	}
	return false
}

func (p TType) Coerce(other interface{}) TType {
	if other == nil {
		return TType(STOP)
	}
	switch b := other.(type) {
	default:
		return TType(STOP)
	case nil:
		return TType(STOP)
	case TType:
		return b
	case byte:
		return TType(b)
	case int:
		return TType(byte(b))
	case int8:
		return TType(byte(b))
	case int32:
		return TType(byte(b))
	case int64:
		return TType(byte(b))
	case uint:
		return TType(byte(b))
	case uint32:
		return TType(byte(b))
	case uint64:
		return TType(byte(b))
	case float32:
		return TType(byte(int(b)))
	case float64:
		return TType(byte(int(b)))
	}
	return TType(STOP)
}

func (p TType) LessType(other interface{}) bool {
	return p < p.Coerce(other)
}

func (p TType) Less(i, j interface{}) bool {
	cmp, ok := p.Compare(i, j)
	return ok && cmp > 0
}

func (p TType) Compare(i, j interface{}) (int, bool) {
	if i == j {
		return 0, true
	}
	if i == nil {
		if j == nil {
			return 0, true
		}
		return -1, true
	}
	if j == nil {
		return 1, true
	}
	ci, iok := p.CoerceData(i)
	cj, jok := p.CoerceData(j)
	if iok && !jok {
		return 1, true
	}
	if !iok && jok {
		return -1, true
	}
	// hopefully this doesn't happen as Compare() would continuously return 0, false
	if !iok && !jok {
		return 0, false
	}
	if ci == cj {
		return 0, true
	}
	if ci == nil {
		if cj == nil {
			return 0, true
		}
		return -1, true
	}
	if cj == nil {
		return 1, true
	}
	switch p {
	case STOP, VOID:
		// hopefully this doesn't happen as Compare() would continuously return 0, false
		return 0, false
	case BOOL:
		vi := ci.(bool)
		vj := cj.(bool)
		if vi == vj {
			return 0, true
		}
		if vi == false {
			return -1, true
		}
		return 1, true
	case BYTE:
		vi := ci.(byte)
		vj := cj.(byte)
		if vi == vj {
			return 0, true
		}
		if vi < vj {
			return -1, true
		}
		return 1, true
	case DOUBLE:
		vi := ci.(float64)
		vj := cj.(float64)
		if vi == vj {
			return 0, true
		}
		if vi < vj {
			return -1, true
		}
		return 1, true
	case I16:
		vi := ci.(int16)
		vj := cj.(int16)
		if vi == vj {
			return 0, true
		}
		if vi < vj {
			return -1, true
		}
		return 1, true
	case I32:
		vi := ci.(int32)
		vj := cj.(int32)
		if vi == vj {
			return 0, true
		}
		if vi < vj {
			return -1, true
		}
		return 1, true
	case I64:
		vi := ci.(int64)
		vj := cj.(int64)
		if vi == vj {
			return 0, true
		}
		if vi < vj {
			return -1, true
		}
		return 1, true
	case STRING, UTF8, UTF16:
		vi := ci.(string)
		vj := cj.(string)
		if vi == vj {
			return 0, true
		}
		if vi < vj {
			return -1, true
		}
		return 1, true
	case STRUCT:
		si := ci.(TStruct)
		sj := cj.(TStruct)
		if cmp := CompareString(si.ThriftName(), sj.ThriftName()); cmp != 0 {
			return cmp, true
		}
		if cmp, ok := si.TStructFields().CompareTo(sj.TStructFields()); !ok || cmp != 0 {
			return cmp, ok
		}
		for field := range si.TStructFields().Iter() {
			a := si.AttributeFromFieldId(field.Id())
			b := sj.AttributeFromFieldId(field.Id())
			if cmp, ok := field.TypeId().Compare(a, b); !ok || cmp != 0 {
				return cmp, ok
			}
		}
		return 0, true
	case MAP:
		mi := ci.(TMap)
		mj := cj.(TMap)
		ei := mi.KeyType()
		if ej := mj.KeyType(); ei != ej {
			return CompareInt(int(ei), int(ej)), true
		}
		if size := mi.Len(); size != mj.Len() {
			return CompareInt(size, mj.Len()), true
		}
		if c, cok := ei.Compare(mi.Keys(), mj.Keys()); c != 0 || !cok {
			return c, cok
		}
		return ei.Compare(mi.Values(), mj.Values())
	case LIST:
		li := ci.(TList)
		lj := cj.(TList)
		ei := li.ElemType()
		ej := lj.ElemType()
		if ei != ej {
			return CompareInt(int(ei), int(ej)), true
		}
		size := li.Len()
		if size != lj.Len() {
			return CompareInt(size, lj.Len()), true
		}
		for k := 0; k < size; k++ {
			vi := li.At(k)
			vj := lj.At(k)
			c, cok := ei.Compare(vi, vj)
			if c != 0 || !cok {
				return c, cok
			}
		}
		return 0, true
	case SET:
		li := ci.(TSet)
		lj := cj.(TSet)
		ei := li.ElemType()
		ej := lj.ElemType()
		if ei != ej {
			return CompareInt(int(ei), int(ej)), true
		}
		size := li.Len()
		if size != lj.Len() {
			return CompareInt(size, lj.Len()), true
		}
		return ei.Compare(li.Values(), lj.Values())
	default:
		panic("Invalid thrift type to coerce")
	}
	return 0, false
}

func (p TType) CompareValueArrays(li, lj []interface{}) (int, bool) {
	size := len(li)
	if cmp := CompareInt(size, len(lj)); cmp != 0 {
		return cmp, true
	}
	for i := 0; i < size; i++ {
		vi := li[i]
		vj := lj[i]
		c, cok := p.Compare(vi, vj)
		if c != 0 || !cok {
			return c, cok
		}
	}
	return 0, true
}

func (p TType) Equals(other interface{}) bool {
	return p == p.Coerce(other)
}

type Stringer interface {
	String() string
}

type Enumer interface {
	String() string
	Value() int
	IsEnum() bool
}

func TypeFromValue(data interface{}) TType {
	switch data.(type) {
	default:
		return STOP
	case nil:
		return VOID
	case bool:
		return BOOL
	case float32, float64:
		return DOUBLE
	case int, int32:
		return I32
	case byte:
		return BYTE
	case int8:
		return I08
	case int16:
		return I16
	case int64:
		return I64
	case string:
		return STRING
	case TStruct:
		return STRUCT
	case TMap:
		return MAP
	case TSet:
		return SET
	case []interface{}, *list.List, TVector, TList:
		return LIST
	}
	return STOP
}

func (p TType) CoerceData(data interface{}) (interface{}, bool) {
	if data == nil {
		switch p {
		case STOP:
			return nil, true
		case VOID:
			return nil, true
		case BOOL:
			return false, true
		case BYTE:
			return byte(0), true
		case DOUBLE:
			return float64(0), true
		case I16:
			return int16(0), true
		case I32:
			return int32(0), true
		case I64:
			return int64(0), true
		case STRING, UTF8, UTF16:
			return "", true
		case STRUCT:
			return NewTStructEmpty(""), true
		case MAP:
			return NewTMapDefault(), true
		case LIST:
			return NewTListDefault(), true
		case SET:
			return NewTSetDefault(), true
		default:
			panic("Invalid thrift type to coerce")
		}
	}
	switch p {
	case STOP:
		return nil, true
	case VOID:
		return nil, true
	case BOOL:
		switch b := data.(type) {
		default:
			return false, false
		case bool:
			return b, true
		case Numeric:
			return bool(b.Int() != 0), true
		case int:
			return b != 0, true
		case byte:
			return b != 0, true
		case int8:
			return b != 0, true
		case int16:
			return b != 0, true
		case int32:
			return b != 0, true
		case int64:
			return b != 0, true
		case uint:
			return b != 0, true
		case uint16:
			return b != 0, true
		case uint32:
			return b != 0, true
		case uint64:
			return b != 0, true
		case float32:
			return b != 0, true
		case float64:
			return b != 0, true
		case Stringer:
			v := b.String()
			if v == "false" || v == "0" || len(v) == 0 {
				return false, true
			}
			return true, true
		case string:
			if b == "false" || b == "0" || len(b) == 0 {
				return false, true
			}
			return true, true
		}
	case BYTE:
		if b, ok := data.(byte); ok {
			return b, true
		}
		if b, ok := data.(Numeric); ok {
			return b.Byte(), true
		}
		if b, ok := data.(bool); ok {
			if b {
				return byte(1), true
			}
			return byte(0), true
		}
		if b, ok := data.(int); ok {
			return byte(b), true
		}
		if b, ok := data.(int8); ok {
			return byte(b), true
		}
		if b, ok := data.(int16); ok {
			return byte(b), true
		}
		if b, ok := data.(int32); ok {
			return byte(b), true
		}
		if b, ok := data.(int64); ok {
			return byte(b), true
		}
		if b, ok := data.(uint); ok {
			return byte(b), true
		}
		if b, ok := data.(uint8); ok {
			return byte(b), true
		}
		if b, ok := data.(uint16); ok {
			return byte(b), true
		}
		if b, ok := data.(uint32); ok {
			return byte(b), true
		}
		if b, ok := data.(uint64); ok {
			return byte(b), true
		}
		if b, ok := data.(float32); ok {
			return byte(int(b)), true
		}
		if b, ok := data.(float64); ok {
			return byte(int(b)), true
		}
		if b, ok := data.(Stringer); ok {
			data = b.String()
		}
		if b, ok := data.(string); ok {
			i1, err := strconv.Atoi(b)
			if err != nil {
				return byte(int(i1)), true
			}
		}
		return byte(0), false
	case DOUBLE:
		if b, ok := data.(float32); ok {
			return float64(b), true
		}
		if b, ok := data.(float64); ok {
			return b, true
		}
		if b, ok := data.(Numeric); ok {
			return bool(b.Float64() != 0.0), true
		}
		if b, ok := data.(byte); ok {
			return float64(b), true
		}
		if b, ok := data.(bool); ok {
			if b {
				return float64(1.0), true
			}
			return float64(0.0), true
		}
		if b, ok := data.(int); ok {
			return float64(b), true
		}
		if b, ok := data.(int8); ok {
			return float64(b), true
		}
		if b, ok := data.(int16); ok {
			return float64(b), true
		}
		if b, ok := data.(int32); ok {
			return float64(b), true
		}
		if b, ok := data.(int64); ok {
			return float64(b), true
		}
		if b, ok := data.(uint); ok {
			return float64(b), true
		}
		if b, ok := data.(uint8); ok {
			return float64(b), true
		}
		if b, ok := data.(uint16); ok {
			return float64(b), true
		}
		if b, ok := data.(uint32); ok {
			return float64(b), true
		}
		if b, ok := data.(uint64); ok {
			return float64(b), true
		}
		if b, ok := data.(Stringer); ok {
			data = b.String()
		}
		if b, ok := data.(string); ok {
			d1, err := strconv.ParseFloat(b, 64)
			if err != nil {
				return d1, true
			}
		}
		return float64(0), false
	case I16:
		if b, ok := data.(int16); ok {
			return b, true
		}
		if b, ok := data.(int); ok {
			return int16(b), true
		}
		if b, ok := data.(Numeric); ok {
			return bool(b.Int16() != 0), true
		}
		if b, ok := data.(byte); ok {
			return int16(b), true
		}
		if b, ok := data.(bool); ok {
			if b {
				return int16(1.0), true
			}
			return int16(0.0), true
		}
		if b, ok := data.(int8); ok {
			return int16(b), true
		}
		if b, ok := data.(int32); ok {
			return int16(b), true
		}
		if b, ok := data.(int64); ok {
			return int16(b), true
		}
		if b, ok := data.(uint); ok {
			return int16(b), true
		}
		if b, ok := data.(uint8); ok {
			return int16(b), true
		}
		if b, ok := data.(uint16); ok {
			return int16(b), true
		}
		if b, ok := data.(uint32); ok {
			return int16(b), true
		}
		if b, ok := data.(uint64); ok {
			return int16(b), true
		}
		if b, ok := data.(float32); ok {
			return int16(int(b)), true
		}
		if b, ok := data.(float64); ok {
			return int16(int(b)), true
		}
		if b, ok := data.(Stringer); ok {
			data = b.String()
		}
		if b, ok := data.(string); ok {
			i1, err := strconv.Atoi(b)
			if err != nil {
				return int16(i1), true
			}
		}
		return int16(0), false
	case I32:
		if b, ok := data.(int32); ok {
			return b, true
		}
		if b, ok := data.(int); ok {
			return int32(b), true
		}
		if b, ok := data.(Numeric); ok {
			return bool(b.Int32() != 0), true
		}
		if b, ok := data.(byte); ok {
			return int32(b), true
		}
		if b, ok := data.(bool); ok {
			if b {
				return int32(1.0), true
			}
			return int32(0.0), true
		}
		if b, ok := data.(int8); ok {
			return int32(b), true
		}
		if b, ok := data.(int16); ok {
			return int32(b), true
		}
		if b, ok := data.(int64); ok {
			return int32(b), true
		}
		if b, ok := data.(uint); ok {
			return int32(b), true
		}
		if b, ok := data.(uint8); ok {
			return int32(b), true
		}
		if b, ok := data.(uint16); ok {
			return int32(b), true
		}
		if b, ok := data.(uint32); ok {
			return int32(b), true
		}
		if b, ok := data.(uint64); ok {
			return int32(b), true
		}
		if b, ok := data.(float32); ok {
			return int32(int(b)), true
		}
		if b, ok := data.(float64); ok {
			return int32(int(b)), true
		}
		if b, ok := data.(Stringer); ok {
			data = b.String()
		}
		if b, ok := data.(string); ok {
			i1, err := strconv.Atoi(b)
			if err != nil {
				return int32(i1), true
			}
		}
		return int32(0), false
	case I64:
		if b, ok := data.(int64); ok {
			return b, true
		}
		if b, ok := data.(int32); ok {
			return int64(b), true
		}
		if b, ok := data.(int); ok {
			return int64(b), true
		}
		if b, ok := data.(Numeric); ok {
			return bool(b.Int64() != 0), true
		}
		if b, ok := data.(byte); ok {
			return int64(b), true
		}
		if b, ok := data.(bool); ok {
			if b {
				return int64(1.0), true
			}
			return int64(0.0), true
		}
		if b, ok := data.(int8); ok {
			return int64(b), true
		}
		if b, ok := data.(int16); ok {
			return int64(b), true
		}
		if b, ok := data.(uint); ok {
			return int64(b), true
		}
		if b, ok := data.(uint8); ok {
			return int64(b), true
		}
		if b, ok := data.(uint16); ok {
			return int64(b), true
		}
		if b, ok := data.(uint32); ok {
			return int64(b), true
		}
		if b, ok := data.(uint64); ok {
			return int64(b), true
		}
		if b, ok := data.(float32); ok {
			return int64(b), true
		}
		if b, ok := data.(float64); ok {
			return int64(b), true
		}
		if b, ok := data.(Stringer); ok {
			data = b.String()
		}
		if b, ok := data.(string); ok {
			i1, err := strconv.ParseInt(b, 10, 64)
			if err != nil {
				return i1, true
			}
		}
		return int64(0), false
	case STRING, UTF8, UTF16:
		if b, ok := data.(Enumer); ok {
			if i1, ok := data.(int); ok {
				return i1, true
			}
			return b.String(), true
		}
		if b, ok := data.(Stringer); ok {
			return b.String(), true
		}
		if b, ok := data.(string); ok {
			return b, true
		}
		if b, ok := data.(int); ok {
			return string(b), true
		}
		if b, ok := data.(byte); ok {
			return string(b), true
		}
		if b, ok := data.(bool); ok {
			if b {
				return "true", true
			}
			return "false", true
		}
		if b, ok := data.(int8); ok {
			return string(b), true
		}
		if b, ok := data.(int16); ok {
			return string(b), true
		}
		if b, ok := data.(int32); ok {
			return string(b), true
		}
		if b, ok := data.(int64); ok {
			return string(b), true
		}
		if b, ok := data.(uint); ok {
			return string(b), true
		}
		if b, ok := data.(uint8); ok {
			return string(b), true
		}
		if b, ok := data.(uint16); ok {
			return string(b), true
		}
		if b, ok := data.(uint32); ok {
			return string(b), true
		}
		if b, ok := data.(uint64); ok {
			return string(b), true
		}
		if b, ok := data.(float32); ok {
			return strconv.FormatFloat(float64(b), 'g', -1, 32), true
		}
		if b, ok := data.(float64); ok {
			return strconv.FormatFloat(b, 'g', -1, 64), true
		}
		return "", false
	case STRUCT:
		if b, ok := data.(TStruct); ok {
			return b, true
		}
		return NewTStructEmpty(""), true
	case MAP:
		if b, ok := data.(TMap); ok {
			return b, true
		}
		return NewTMapDefault(), false
	case LIST:
		if b, ok := data.(TList); ok {
			return b, true
		}
		return NewTListDefault(), false
	case SET:
		if b, ok := data.(TSet); ok {
			return b, true
		}
		return NewTSetDefault(), false
	default:
		panic("Invalid thrift type to coerce")
	}
	return nil, false
}

type EqualsOtherInterface interface {
	Equals(other interface{}) bool
}

type EqualsMap interface {
	Equals(other TMap) bool
}

type EqualsSet interface {
	Equals(other TSet) bool
}

type EqualsList interface {
	Equals(other TList) bool
}

type EqualsStruct interface {
	Equals(other TStruct) bool
}
